﻿
using System;
using System.Text;
using PsqlDotnet.Enums;
using PsqlDotnet.Exceptions;
namespace PsqlDotnet.Model.Schema.Converters
{
    public class SchemaConverter : ISchemaConverter
    {
        #region ISchemaConverter Members

        public string GetDDL(ITable table)
        {
            if (table == null)
                throw new NullReferenceException("Illegal null-reference argument");


            StringBuilder sb = new StringBuilder();

            sb.Append("CREATE TABLE ");
            sb.AppendLine(table.Name);
            sb.AppendLine("(");

            bool hasConstraints = table.Constraints.Count > 0;

            var columnsCollection = table.Columns;
            int index = 1;
            int lastIndex = columnsCollection.Count;

            foreach (var c in table.Columns)
            {
                sb.Append(GetDDL(c));
                if (index != lastIndex || hasConstraints)
                    sb.AppendLine(",");
                else
                    sb.AppendLine();

                ++index;
            }

            index = 1;
            lastIndex = table.Constraints.Count;
            foreach (var c in table.Constraints)
            {
                sb.Append(GetDDL(c));
                if (index != lastIndex)
                    sb.AppendLine(",");
                else
                    sb.AppendLine();

                ++index;
            }


            sb.AppendLine(")");
            sb.AppendLine("WITH ( OIDS=FALSE );");

            return sb.ToString();
        }

        public string GetDDL(Column column)
        {
            return String.Format(
                "{0} {1} {2}",
                column.Name,
                ConvertToString(column.DataType, column.FieldSize, column.Precision),
                column.IsNullable ? "NULL" : "NOT NULL");
        }

        public string GetDDL(IConstraint constraint)
        {
            if (constraint is PrimaryKey)
                return GetPrimaryKeyDDL((PrimaryKey)constraint);

            if (constraint is ForeignKey)
                return GetForeignKeyDDL((ForeignKey)constraint);

            if (constraint is UniqueConstraint)
                return GetUniqueConstraintDDL((UniqueConstraint)constraint);

            throw new OperationCanceledException("Unknown constraint type");
        }

        #endregion

        #region Private methods

        private string ConvertToString(DbDataTypeEnum dataType, int fieldSize, int precision)
        {
            switch (dataType)
            {
                case DbDataTypeEnum.Serial:
                    return "serial";
                case DbDataTypeEnum.Integer:
                    return "integer";
                case DbDataTypeEnum.Date:
                    return "date";
                case DbDataTypeEnum.Bigint:
                    return "bigint";
                case DbDataTypeEnum.Boolean:
                    return "boolean";
                case DbDataTypeEnum.Box:
                    return "box";
                case DbDataTypeEnum.Bytea:
                    return "bytea";
                case DbDataTypeEnum.Cidr:
                    return "cidr";
                case DbDataTypeEnum.Circle:
                    return "circle";
                case DbDataTypeEnum.DoublePrecision:
                    return "double precision";
                case DbDataTypeEnum.Inet:
                    return "inet";
                case DbDataTypeEnum.Line:
                    return "line";
                case DbDataTypeEnum.Lseg:
                    return "lseg";
                case DbDataTypeEnum.Macaddr:
                    return "macaddr";
                case DbDataTypeEnum.Money:
                    return "money";
                case DbDataTypeEnum.Path:
                    return "path";
                case DbDataTypeEnum.Point:
                    return "point";
                case DbDataTypeEnum.Polygon:
                    return "polygon";
                case DbDataTypeEnum.Real:
                    return "real";
                case DbDataTypeEnum.Smallint:
                    return "smallint";
                case DbDataTypeEnum.Text:
                    return "text";
                case DbDataTypeEnum.Tsquery:
                    return "tsquery";
                case DbDataTypeEnum.Tsvector:
                    return "tsvector";
                case DbDataTypeEnum.Txid_snapshot:
                    return "txid_snapshot";
                case DbDataTypeEnum.Uuid:
                    return "uuid";
                case DbDataTypeEnum.Xml:
                    return "xml";

                case DbDataTypeEnum.CharacterVarying:
                    {
                        if (fieldSize > 0)
                            return String.Format("character varying({0})", fieldSize);
                        else
                            return "character varying";
                    }
                case DbDataTypeEnum.Bit:
                    {
                        if (fieldSize > 1)
                            return String.Format("bit({0})", fieldSize);
                        else
                            return "bit";
                    }
                case DbDataTypeEnum.BitVarying:
                    {
                        if (fieldSize > 1)
                            return String.Format("bit varying({0})", fieldSize);
                        else
                            return "bit varying";
                    }
                case DbDataTypeEnum.Character:
                    {
                        if (fieldSize > 0)
                            return String.Format("character({0})", fieldSize);
                        else
                            return "character";
                    }
                case DbDataTypeEnum.Numeric:
                    {
                        if (fieldSize > 0 && precision > 0)
                            return String.Format("numeric({0},{1})", fieldSize, precision);
                        else
                            return "numeric";
                    }
                case DbDataTypeEnum.TimeWithTimeZone:
                    {
                        if (fieldSize > 0)
                            return String.Format("time with time zone({0})", fieldSize);
                        else
                            return "time with time zone";
                    }
                case DbDataTypeEnum.TimeWithoutTimeZone:
                    {
                        if (fieldSize > 0)
                            return String.Format("time without time zone({0})", fieldSize);
                        else
                            return "time without time zone";
                    }
                case DbDataTypeEnum.TimestampWithTimeZone:
                    {
                        if (fieldSize > 0)
                            return String.Format("timestamp with time zone({0})", fieldSize);
                        else
                            return "timestamp with time zone";
                    }
                case DbDataTypeEnum.TimestampWithoutTimeZone:
                    {
                        if (fieldSize > 0)
                            return String.Format("timestamp without time zone({0})", fieldSize);
                        else
                            return "timestamp without time zone";
                    }
            }

            throw new PsqlException("Unsupported data type for column");
        }

        private string GetPrimaryKeyDDL(PrimaryKey key)
        {
            return String.Format("CONSTRAINT {0} PRIMARY KEY ({1})", key.Name, String.Join(",", key.SourceColumns));
        }

        private string GetForeignKeyDDL(ForeignKey key)
        {
            StringBuilder sb = new StringBuilder(
                String.Format("CONSTRAINT {0} FOREIGN KEY ({1}) REFERENCES {2} ({3})",
                key.Name, 
                String.Join(",", key.SourceColumns), 
                key.ReferencedTable, 
                String.Join(",", key.ReferencedColumns))
            );
            sb.Append(
                String.Format(" {0} ON UPDATE {1} ON DELETE {2}",
                MatchTypeToString(key.MatchType),
                ActionToString(key.UpdateAction),
                ActionToString(key.DeleteAction)));

            return sb.ToString();
        }

        private string GetUniqueConstraintDDL(UniqueConstraint constraint)
        {
            return String.Format("CONSTRAINT {0} UNIQUE ({1})", constraint.Name, constraint.SourceColumn);
        }

        private string MatchTypeToString(ForeignKeyMatchEnum m)
        {
            switch (m)
            {
                case ForeignKeyMatchEnum.FullMatch:
                    return "MATCH FULL";
                case ForeignKeyMatchEnum.PartialMatch:
                    return "MATCH PARTIAL";
                case ForeignKeyMatchEnum.SimpleMatch:
                    return "MATCH SIMPLE";
            }

            throw new OperationCanceledException("Unkown foreign key match type");
        }

        private string ActionToString(ForeignKeyActionEnum a)
        {
            switch (a)
            {
                case ForeignKeyActionEnum.Cascade:
                    return "CASCADE";
                case ForeignKeyActionEnum.NoAction:
                    return "NO ACTION";
                case ForeignKeyActionEnum.Restrict:
                    return "RESTRICT";
                case ForeignKeyActionEnum.SetDefault:
                    return "SET DEFAULT";
                case ForeignKeyActionEnum.SetNull:
                    return "SET NULL";
            }

            throw new OperationCanceledException("Unknown foreign key onDelete/onUpdateAction");
        }

        #endregion
    }
}
